package net.towee.server.rpc.servlet;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.towee.model.account.AccountData;
import net.towee.model.account.AccountGroup;
import net.towee.model.account.AccountGroupsContainer;
import net.towee.model.account.AccountIdentity;
import net.towee.model.account.AccountPermission;
import net.towee.model.account.AccountProfile;
import net.towee.model.account.Board;
import net.towee.model.account.ContactsGroup;
import net.towee.model.account.Gadget;
import net.towee.model.account.Role;
import net.towee.model.account.impl.ContactsGroupImpl;
import net.towee.model.discussion.Discussion;
import net.towee.model.discussion.DiscussionSubject;
import net.towee.model.discussion.Message;
import net.towee.model.group.GroupData;
import net.towee.model.group.GroupEnterRequest;
import net.towee.model.group.GroupIdentity;
import net.towee.model.group.GroupProfile;
import net.towee.model.group.impl.GroupEnterRequestImpl;
import net.towee.model.repo.AttachmentFactory;
import net.towee.model.repo.AttachmentIdentity;
import net.towee.model.repo.AttachmentInfo;
import net.towee.model.repo.DocumentData;
import net.towee.model.repo.DocumentFactory;
import net.towee.model.repo.DocumentIdentity;
import net.towee.model.repo.DocumentPatch;
import net.towee.model.repo.DocumentType;
import net.towee.model.repo.NodeData;
import net.towee.model.utils.Pair;
import net.towee.server.authentication.EmailSender;
import net.towee.server.authentication.SessionAuthManager;
import net.towee.server.persistence.AccountStore;
import net.towee.server.persistence.AttachmentStore;
import net.towee.server.persistence.DiscussionStore;
import net.towee.server.persistence.DocumentStore;
import net.towee.server.persistence.GroupStore;
import net.towee.server.util.RandomBase64Generator;
import net.towee.shared.exceptions.NameExistsException;
import net.towee.shared.exceptions.PersistenceException;

import org.apache.vysper.xmpp.server.ServerRuntimeContext;
import org.bson.types.ObjectId;

import com.google.gwt.thirdparty.guava.common.base.Preconditions;
import com.google.gwt.thirdparty.guava.common.base.Strings;
import com.google.inject.Inject;
import com.mycila.event.Dispatcher;

/**
 * Towee service front-end
 * 
 * @author c58
 */
public class ToweeServiceServlet extends AbstractToweeService {
	private final RandomBase64Generator gen = new RandomBase64Generator();
	
	private final AccountStore accountStore;
	private final GroupStore groupStore;
	private final AttachmentFactory attachmentFactory;
	private final AttachmentStore attachmentStore;
	private final DiscussionStore discussionStore;
	private final DocumentStore documentStore;
	private final DocumentFactory documentFactory;
	private final EmailSender emailSender;

	@Inject
	public ToweeServiceServlet(Dispatcher dispatcher, SessionAuthManager sessionManager,
			ServerRuntimeContext xmppServer, AccountStore accountStore, GroupStore groupStore,
			AttachmentFactory attachmentFactory, AttachmentStore attachmentStore,
			DiscussionStore discussionStore, DocumentStore documentStore, DocumentFactory documentFactory,
			EmailSender emailSender) {
		super(dispatcher, sessionManager, xmppServer);

		this.accountStore = accountStore;
		this.groupStore = groupStore;
		this.attachmentFactory = attachmentFactory;
		this.attachmentStore = attachmentStore;
		this.discussionStore = discussionStore;
		this.documentStore = documentStore;
		this.documentFactory = documentFactory;
		this.emailSender = emailSender;
	}

	private String generateSubscriberNode(String... path) {
		StringBuilder str = new StringBuilder();

		for (String s : path)
			str.append("/" + s);

		return str.toString();
	}

	@Override
	protected void initSubscribers() {
		// All events only for current logged in user
		addSubscriber(generateSubscriberNode("user", getUserId()));

		// Add groups subscribers
		try {
			for (AccountGroup g : accountStore.getAccountById(getUserId(), "accountGroups").getGroups()
					.getGroupsList()) {
				addSubscriber(generateSubscriberNode("group", g.getGroupIdentity().getId()));
			}
		} catch (PersistenceException e) {
		}
	}

	/**
	 * @return id of logged in user of current session
	 */
	protected String getUserId() {
		return sessionManager.getLoggedInUser(getSession(), null);
	}

	/**
	 * @param groupId
	 * @param perm
	 * @return true if user have all permissions in given group
	 */
	private boolean hasPermissions(String id, AccountPermission... perm) {
		try {
			AccountGroupsContainer cont = accountStore.getAccountById(getUserId(), "accountGroups")
					.getGroups();
			return cont.isInGroups(id) && cont.getAccountGroup(id).getRole().hasPermissions(perm);
		} catch (PersistenceException e) {
			// TODO log
			return false;
		}
	}

	@Override
	public AccountData getCurrentAccount() {
		try {
			return accountStore.getAccountById(getUserId());
		} catch (PersistenceException e) {
			return null;
		}
	}

	@Override
	public String updateCookieId() {
		String id = gen.next(50);
		accountStore.updateAccountCookie(getUserId(), id);
		return id;
	}
	
	@Override
	public AccountProfile getAccountProfile(String accountId) throws PersistenceException {
		Preconditions.checkNotNull(accountId, "null account id");
		return accountStore.getProfile(getUserId(), accountId);
	}

	@Override
	public AccountIdentity getAccountIdentity(String accountId) throws PersistenceException {
		Preconditions.checkNotNull(accountId, "null account id");
		return accountStore.getAccountIdentity(accountId);
	}

	@Override
	public List<AccountIdentity> findAccount(String str, int offset, int limit) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(str), "null account search string");
		return accountStore.findUsers(str, null, null, offset, limit);
	}

	@Override
	public boolean checkAccountWithNicknameExists(String nickname) {
		Preconditions.checkNotNull(!Strings.isNullOrEmpty(nickname), "null nickname");
		return accountStore.checkAccountWithNickName(nickname);
	}

	@Override
	public boolean updateAccountIdentity(AccountIdentity identity) throws PersistenceException {
		Preconditions.checkNotNull(identity, "null account identity");
		return accountStore.updateAccountIdentity(getUserId(), identity);
	}

	@Override
	public boolean updateAccountProfile(AccountProfile profile) throws PersistenceException {
		Preconditions.checkNotNull(profile, "null account profile");
		return accountStore.updateProfile(getUserId(), profile);
	}

	@Override
	public boolean putSetting(String key, String val) {
		return accountStore.updateSetting(getUserId(), key, val);
	}

	@Override
	public boolean updateSettings(Map<String, String> settingsMap) {
		return accountStore.updateSettings(getUserId(), settingsMap);
	}

	@Override
	public AttachmentIdentity createAttachmentIdentity(String rawAttachmentId)
			throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(rawAttachmentId), "null attachemtn raw id");

		// Create attachment from given raw id
		AttachmentInfo att = attachmentFactory.createAttachment(rawAttachmentId);

		// Check result
		if (att == null)
			throw new PersistenceException();

		return att.getIdentity();
	}

	@Override
	public AttachmentInfo getAttachmentInfo(String attachmentId) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(attachmentId), "null attachemtn raw id");
		return attachmentStore.getAttachmentInfo(attachmentId);
	}

	@Override
	public ContactsGroup createContactsGroup(String name) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "null contacts group name");

		// Create group
		ContactsGroup group = new ContactsGroupImpl(new ObjectId().toString());
		group.setName(name);

		// Save it
		Preconditions.checkArgument(accountStore.addContactsGroup(getUserId(), group),
				"can't create contacts group");
		return group;
	}

	@Override
	public boolean removeContactsGroup(String id) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "null contacts group id");

		return accountStore.removeContactsGroup(getUserId(), id);
	}

	@Override
	public boolean renameContactsGroup(String id, String newName) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "null contacts group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(newName), "null new contacts group name");

		return accountStore.renameContactsGroup(id, newName);
	}

	@Override
	public boolean addContactsToGroup(String id, List<AccountIdentity> accounts)
			throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "null contacts group id");
		Preconditions.checkNotNull(accounts, "null accounts list");
		Preconditions.checkArgument(accounts.size() > 0, "null accounts list");

		// Get our account identity
		AccountData user = accountStore.getAccountById(getUserId(), "identity", "contacts");

		// Add contacts and fire events
		for (AccountIdentity account : accounts) {
			if (accountStore.addContactToGroup(id, account) != true)
				return false;

			// Fire an event
			onUserAddedToContacts(new Pair<AccountIdentity, String>(user.getAccountIdentity(), user
					.getContacts().getContactsGroup(id).getName()),
					generateSubscriberNode("user", account.getId()));
		}

		return true;
	}

	@Override
	public boolean removeContactsForomGroup(String id, List<String> accountIds)
			throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "null contacts group id");
		Preconditions.checkNotNull(accountIds, "null accounts list");
		Preconditions.checkArgument(accountIds.size() > 0, "null accounts list");

		// Get our account identity
		AccountData account = accountStore.getAccountById(getUserId(), "identity", "contacts");

		// Fire an event
		for (String acc : accountIds)
			onUserRemovedFromContacts(new Pair<AccountIdentity, String>(account.getAccountIdentity(),
					account.getContacts().getContactsGroup(id).getName()),
					generateSubscriberNode("user", acc));

		return accountStore.removeContactFromGroup(id, accountIds.toArray(new String[0]));
	}

	@Override
	public Board addBoard(Board board) {
		Preconditions.checkNotNull(board, "null board");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(board.getName()), "null board name");

		// Set id for group
		board.setId(new ObjectId().toString());

		// Save board
		accountStore.addBoard(getUserId(), board);

		return board;
	}

	@Override
	public boolean removeBoard(String boardId) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(boardId), "null board id");

		return accountStore.removeBoard(getUserId(), boardId);
	}

	@Override
	public boolean renameBoard(String boardId, String newName) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(boardId), "null board id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(newName), "null board new name");

		return accountStore.renameBoard(getUserId(), boardId, newName);
	}

	@Override
	public GroupIdentity attachGroupToBoard(String boardId, String groupId) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(boardId), "null board id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");

		// Attach group
		if (accountStore.attachGroupToBoard(getUserId(), boardId, groupId))
			return groupStore.getGroupIdentity(groupId);

		return null;
	}

	@Override
	public boolean detachGroupFromBoard(String boardId, String groupId) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(boardId), "null board id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");

		return accountStore.detachGroupFromBoard(getUserId(), boardId, groupId);
	}

	@Override
	public void updateBoard(Board currentBoard) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(currentBoard.getId()), "null board id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(currentBoard.getName()), "null board name");

		accountStore.updateBoard(getUserId(), currentBoard);
	}

	@Override
	public Discussion getDiscussion(String id, DiscussionSubject subject, int offset, int limit)
			throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "null discussion id");
		Preconditions.checkArgument(offset >= 0, "bad offset");
		Preconditions.checkArgument(limit >= 0, "bad limit");

		// Get discussion
		Discussion disc = discussionStore.getDiscussion(id, subject, offset, limit);

		// Check permissions
		if (subject.equals(DiscussionSubject.DOCUMENT)) {
			if (accountStore.isInGroups(getUserId(), documentStore.getDocumentIdentity(id).getGroup()
					.getId()))
				return disc;
			else
				return null;
		}

		// TODO: How to check for attachment?
		return null;
	}

	@Override
	public String addDiscussionMessage(String discId, DiscussionSubject subject, Message mess)
			throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(discId), "null discussion id");
		Preconditions.checkNotNull(mess, "null message");
		Preconditions.checkNotNull(subject, "null discussion subject");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(mess.getText()), "null text");

		// Get group id
		String groupId = null;
		if (subject.equals(DiscussionSubject.DOCUMENT))
			groupId = documentStore.getDocumentIdentity(discId).getGroup().getId();

		// Check permissions
		if (subject.equals(DiscussionSubject.DOCUMENT)
				&& !hasPermissions(groupId, AccountPermission.CAN_ADD_MESSAGES))
			return null;

		// TODO: How to check for attachment?

		// Set data
		mess.setId(new ObjectId().toString());
		mess.setAuthor(accountStore.getAccountIdentity(getUserId()));

		// Save mesage
		if (discussionStore.addMessage(discId, subject, mess)) {
			// Fire an event
			if (subject.equals(DiscussionSubject.DOCUMENT))
				onDiscussionMessageUpdate(new Pair<String, Message>(discId, mess),
						generateSubscriberNode("group", groupId));

			return mess.getId();
		}

		return null;
	}

	@Override
	public boolean changeDiscussionMessage(String discId, DiscussionSubject subject, Message mess)
			throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(discId), "null discussion id");
		Preconditions.checkNotNull(mess, "null message");
		Preconditions.checkNotNull(mess.getAuthor(), "null message author");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(mess.getText()), "null message text");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(mess.getId()), "null messsage id");

		// Get group id
		String groupId = null;
		if (subject.equals(DiscussionSubject.DOCUMENT))
			groupId = documentStore.getDocumentIdentity(discId).getGroup().getId();

		if (subject.equals(DiscussionSubject.DOCUMENT)
				&& !hasPermissions(groupId, AccountPermission.CAN_CHANGE_OTHER_MESSAGES)
				&& !mess.getAuthor().getId().equals(getUserId()))
			return false;

		// Set author for current logged in user
		mess.setAuthor(accountStore.getAccountIdentity(getUserId()));

		// Try to change message
		if (discussionStore.changeMessage(discId, subject, mess)) {
			// Fire an event
			if (subject.equals(DiscussionSubject.DOCUMENT))
				onDiscussionMessageUpdate(new Pair<String, Message>(discId, mess),
						generateSubscriberNode("group", groupId));

			return true;
		}

		return false;
	}

	@Override
	public List<DocumentData> findDocument(String str, List<String> groupId, String authorId,
			List<DocumentType> type, int offset, int limit) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(str), "null search string");
		Preconditions.checkArgument(accountStore.isInGroups(getUserId(), groupId.toArray(new String[0])),
				"user not in groups");

		return documentStore.findDocuments(str, groupId, authorId, type, offset, limit);
	}

	@Override
	public List<DocumentData> getDocuments(List<String> groupId, List<DocumentType> types, int offset,
			int limit) {
		Preconditions.checkNotNull(groupId, "null groupIds list");
		Preconditions.checkArgument(accountStore.isInGroups(getUserId(), groupId.toArray(new String[0])),
				"user not in groups");

		return documentStore.getDocuments(offset, limit, groupId, types);
	}

	@Override
	public DocumentData getDocuments(String docId) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(docId), "null document id");

		// Get document
		DocumentData doc = documentStore.getDocument(docId);

		// Check permission
		if (!accountStore.isInGroups(getUserId(), doc.getIdentity().getGroup().getId()))
			return null;
		return doc;
	}

	@Override
	public DocumentData saveDocument(DocumentData doc, String parentNode) throws NameExistsException,
			PersistenceException {
		Preconditions.checkNotNull(doc, "null document for saving");

		// Validate given document
		doc = documentFactory.createDocument(doc, parentNode);
		doc.getIdentity().setAuthor(accountStore.getAccountIdentity(getUserId()));

		// Check permissions
		if (!hasPermissions(doc.getIdentity().getGroup().getId(), AccountPermission.CAN_ADD_DOCUMENT))
			return null;

		if (doc == null)
			throw new PersistenceException();

		// Save it in DB
		documentStore.createDocument(doc);

		// Fire an event
		onDocumentUpdate(doc, generateSubscriberNode("group", doc.getIdentity().getGroup().getId()));
		return doc;
	}

	@Override
	public DocumentData changeDocument(DocumentData doc, String explain) throws NameExistsException {
		Preconditions.checkNotNull(doc, "null document for saving");
		Preconditions.checkNotNull(doc.getIdentity(), "null document identity");
		Preconditions.checkNotNull(doc.getIdentity().getGroup(), "null document group");
		Preconditions.checkNotNull(doc.getIdentity().getAuthor(), "null document author");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(explain), "null explain");

		// Check permissions
		if (!doc.getIdentity().getAuthor().getId().equals(getUserId())
				&& !hasPermissions(doc.getIdentity().getGroup().getId(),
						AccountPermission.CAN_CHANGE_OTHER_DOCUMENT))
			return null;

		// Create changeset
		DocumentData changeSet = documentFactory.createChangeset(doc.getIdentity().getId(), doc,
				getUserId(), explain);

		// Save document
		if (documentStore.changeDocument(doc.getIdentity().getId(), changeSet)) {
			// Fire an event
			onDocumentUpdate(changeSet,
					generateSubscriberNode("group", changeSet.getIdentity().getGroup().getId()));
			return changeSet;
		}

		return null;
	}

	@Override
	public boolean tupDocument(String docId) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(docId), "null document id");

		if (documentStore.tupDocument(docId, getUserId())) {
			DocumentIdentity idnt = documentStore.getDocumentIdentity(docId);

			// Fire an event
			onDocumentRated(
					new Pair<AccountIdentity, DocumentIdentity>(accountStore.getAccountIdentity(docId),
							idnt), generateSubscriberNode("group", idnt.getGroup().getId()));

			return true;
		}

		return false;
	}

	@Override
	public Map<String, DocumentPatch> getDocumentPatchs(List<String> neededPatchs)
			throws PersistenceException {
		Preconditions.checkNotNull(neededPatchs, "null patches ids list");

		// TODO: maybe return list of patches and convert to map on client side?
		List<DocumentPatch> list = documentStore.getDocumentPatchs(neededPatchs);
		Map<String, DocumentPatch> patchs = new HashMap<String, DocumentPatch>();

		for (DocumentPatch p : list) {
			patchs.put(p.getId(), p);
		}

		return patchs;
	}

	@Override
	public GroupData getGroupData(String id) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "null id");

		// Create list with fields to retrive
		List<String> retrFields = new ArrayList<String>();
		retrFields.add("_id");
		retrFields.add("identity");
		retrFields.add("testQuest");
		retrFields.add("profile");
		retrFields.add("roles");
		retrFields.add("defaultRole");

		// Add hidden for not permitted users
		if (hasPermissions(id, AccountPermission.CAN_CHANGE_TEST_QUEST))
			retrFields.add("testAnsw");
		if (hasPermissions(id, AccountPermission.CAN_PROCESS_ENTER_REQUESTS))
			retrFields.add("requests");

		// Get group
		return groupStore.getGroupById(id, retrFields.toArray(new String[0]));
	}

	@Override
	public List<GroupProfile> findGroup(String str, int offset, int limit) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(str), "null search string");
		return groupStore.findGroup(str, offset, limit);
	}

	@Override
	public List<Pair<AccountIdentity, AccountGroup>> getGroupMembers(String groupId, int offset, int limit) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");

		return accountStore.getUsersByGroup(groupId, offset, limit);
	}

	@Override
	public List<AccountIdentity> whosOnline(String groupId, int offset, int limit) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(accountStore.isInGroups(getUserId(), groupId), "user not in group");

		return accountStore.whosOnline(groupId, offset, limit);
	}

	@Override
	public List<GroupProfile> getGroupsByIds(List<String> ids) {
		Preconditions.checkNotNull(ids, "null groups for getting");
		Preconditions.checkArgument(accountStore.isInGroups(getUserId(), ids.toArray(new String[0])),
				"user not in groups");
		return groupStore.getGroupsByIds(ids);
	}

	@Override
	public boolean addEnterRequest(String groupId, String accountId, String testAnsw)
			throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(accountId), "null account id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(testAnsw), "null answer");
		Preconditions.checkArgument(!accountStore.isInGroups(getUserId(), groupId),
				"user already in group");

		// Check answer
		if (!groupStore.testGroupAnswer(groupId, testAnsw))
			throw new PersistenceException();

		// Add enter request
		return groupStore.addEnterRequest(groupId,
				new GroupEnterRequestImpl(accountStore.getAccountIdentity(accountId)));
	}

	@Override
	public String getGroupTestQuestion(String groupId) throws PersistenceException {
		return groupStore.getGroupById(groupId, "testQuest").getTestQuestion();
	}

	@Override
	public boolean processEnterRequest(String groupId, String accountId) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(accountId), "null account id");
		Preconditions.checkArgument(
				hasPermissions(groupId, AccountPermission.CAN_PROCESS_ENTER_REQUESTS),
				"no permission for processing request");

		// Try process request
		if (groupStore.processEnterRequest(groupId, accountId)) {

			// Fire an event
			onUserEnterToGroup(groupStore.getGroupIdentity(groupId),
					this.generateSubscriberNode("user", accountId));
			return true;
		}

		return false;
	}

	@Override
	public List<GroupEnterRequest> getEnterRequests(String groupId, int offset, int limit) {
		Preconditions.checkArgument(
				hasPermissions(groupId, AccountPermission.CAN_PROCESS_ENTER_REQUESTS),
				"have not request process permissions");
		return groupStore.getEnterRequests(groupId, offset, limit);
	}

	@Override
	public GroupData createGroup(GroupData group) throws PersistenceException, NameExistsException {
		// Validate given data
		Preconditions.checkNotNull(group, "null group for creating");
		Preconditions.checkNotNull(group.getIdentity(), "null group identity");
		Preconditions.checkNotNull(group.getProfile(), "null group profile");
		Preconditions
				.checkArgument(group.getRoleContainer().getRoleList().size() > 0, "null group roles");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(group.getIdentity().getAlias()),
				"null group alias");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(group.getIdentity().getPrintName()),
				"null group name");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(group.getProfile().getDescription()),
				"null group description");
		Preconditions.checkNotNull(group.getProfile().getOpenness(), "null group openness");

		// Check roles
		Role ownerRole = null;
		Iterator<Role> it = group.getRoleContainer().getRoleList().iterator();

		// Get first owner role and remove other owner
		// roles if they exists
		while (it.hasNext()) {
			Role curRole = it.next();

			if (ownerRole != null && curRole.isOwner()) {
				it.remove();
				continue;
			}

			if (curRole.isOwner())
				ownerRole = curRole;
		}

		// If group doesn't contain some owner role
		if (ownerRole == null)
			return null;

		// Set id of group
		group.setId(new ObjectId().toString());

		// Try to create group
		if (!groupStore.createGroup(group))
			return null;

		// Enter user to created group with owner role
		accountStore.enterToGroup(getUserId(), group.getIdentity(), null, ownerRole);
		return group;
	}

	@Override
	public AccountGroup enterToGroup(String groupId, String testAnswer) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(testAnswer), "null test answer");
		Preconditions.checkArgument(!accountStore.isInGroups(getUserId(), groupId),
				"user already in group");

		// Enter user to group
		AccountGroup ag = accountStore.enterToGroup(getUserId(), groupId, testAnswer);

		if (ag == null)
			return null;
		
		// Add subscriber
		addSubscriber(generateSubscriberNode("group", groupId));
		return ag;
	}

	@Override
	public boolean leaveGroup(String groupId) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(accountStore.isInGroups(getUserId(), groupId), "user not in group");

		// Try leave group
		return accountStore.removeFromGroup(getUserId(), groupId);
	}

	@Override
	public boolean kickUserFromGroup(String groupId, String userId) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(userId), "null user id");
		Preconditions.checkArgument(!userId.equals(getUserId()), "user want kick himself");
		Preconditions.checkArgument(hasPermissions(groupId, AccountPermission.CAN_KICK_USERS),
				"mo kick permission");

		// Remove from group
		if(!accountStore.removeFromGroup(userId, groupId))
			return false;
		
		// Fire an event
		onUserLeaveFromGroup(groupStore.getGroupIdentity(groupId), generateSubscriberNode("user", userId));
		return true;
	}

	@Override
	public boolean updateGroupProfile(GroupProfile group) {
		Preconditions.checkNotNull(group, "null group profile");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(group.getDescription()),
				"null group description");
		Preconditions.checkNotNull(group.getOpenness(), "null group openness");
		Preconditions.checkNotNull(group.getIdentity(), "null group identity");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(group.getIdentity().getId()), "null group id");

		return groupStore.updateProfile(group);
	}

	@Override
	public boolean updatePrintName(String groupId, String newName) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(newName), "null group name");

		return groupStore.updatePrintName(groupId, newName);
	}

	@Override
	public List<NodeData> getRootNodes(String groupId) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");

		return documentStore.getRootTree(groupId);
	}

	@Override
	public List<NodeData> getSubTree(String groupId, String parentNodeId) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(parentNodeId), "null parent node id");

		return documentStore.getSubTree(groupId, parentNodeId);
	}

	@Override
	public List<NodeData> getSubTreeByPath(String groupId, String path) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(path), "null path");

		return documentStore.getSubTreeByPath(groupId, path);
	}

	@Override
	public List<NodeData> getSubTreeToPath(String groupId, String path) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(path), "null path");

		return documentStore.getSubTreesToPath(groupId, path);
	}

	@Override
	public NodeData getNode(String nodeId) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(nodeId), "null node id");

		return documentStore.getNode(nodeId);
	}

	@Override
	public NodeData saveNode(NodeData node) throws PersistenceException {
		Preconditions.checkNotNull(node, "null node");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(node.getGroupId()), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(node.getName()), "null node name");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(node.getPath()), "null node path");
		Preconditions
				.checkArgument(hasPermissions(node.getGroupId(), AccountPermission.CAN_ADD_DOCUMENT));

		// Check existing group and parent node
		if (node.getParentId() != null) {
			NodeData parentNode = documentStore.getNode(node.getParentId());
			node.setGroupId(parentNode.getGroupId());
			node.setPath(parentNode.getPathForChildren());
		} else {
			node.setPath("/");
			node.setCreationDate(new Date());
		}

		// Test group existing
		groupStore.getGroupIdentity(node.getGroupId());

		// Set new id
		node.setId(new ObjectId().toString());

		// Save node
		documentStore.saveNode(node.toTreeNode());

		// Fire an event
		onNodesChange(Arrays.asList(node), generateSubscriberNode("group", node.getGroupId()));

		// Return node
		return node;
	}

	@Override
	public boolean moveNodes(String groupId, String newParentNode, List<String> nodeIds)
			throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(newParentNode), "null parent node id");
		Preconditions.checkNotNull(nodeIds, "null nodes list");
		Preconditions.checkArgument(hasPermissions(groupId, AccountPermission.CAN_MOVE_NODES));

		// Move nodes
		documentStore.moveNodes(groupId, newParentNode, nodeIds.toArray(new String[0]));

		// Fire an event
		onNodesMoved(
				new Pair<String, List<NodeData>>(newParentNode, documentStore.getNodes(groupId, nodeIds)),
				generateSubscriberNode("group", groupId));
		return true;
	}

	@Override
	public boolean renameNode(String groupId, String nodeId, String newName) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(nodeId), "null node id");
		Preconditions.checkArgument(hasPermissions(groupId, AccountPermission.CAN_RENAME_NODES));

		// Fire an event
		onNodesChange(documentStore.getNodes(groupId, Arrays.asList(nodeId)),
				generateSubscriberNode("group", groupId));

		// Rename node
		documentStore.renameNode(groupId, nodeId, newName);
		return true;
	}

	@Override
	public boolean putToTrash(String groupId, List<String> nodeIds) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkNotNull(nodeIds, "null nodes list");
		Preconditions.checkArgument(hasPermissions(groupId, AccountPermission.CAN_PUT_TO_TRASH));

		// On nodes moved to trash
		onNodesToTrash(nodeIds, generateSubscriberNode("group", groupId));

		// Put to trash
		documentStore.putToTrash(groupId, nodeIds.toArray(new String[0]));
		return true;
	}

	@Override
	public boolean removeNode(String groupId, List<String> nodeIds) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkNotNull(nodeIds, "null nodes list");
		Preconditions.checkArgument(hasPermissions(groupId, AccountPermission.CAN_REMOVE_NODE));

		// Fire an event
		onNodesRemoved(documentStore.getNodes(groupId, nodeIds), generateSubscriberNode("group", groupId));

		// Remove nodes
		return documentStore.removeNode(groupId, nodeIds);
	}

	@Override
	public boolean checkNodeExisting(String nodeName, String groupNode, String parentNode) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(nodeName), "null name");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupNode), "null node group");

		return documentStore.checkNodeWithNameExisting(groupNode, parentNode, nodeName);
	}

	@Override
	public Gadget attachGadgetToBoard(String boardId, Gadget g) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(boardId), "null board id");
		Preconditions.checkNotNull(g, "null gadget");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(g.getGadgetName()), "null gadget name");

		if (accountStore.addGadgetToBoard(getUserId(), boardId, g))
			return g;

		return null;
	}

	@Override
	public boolean detachGadgetFromBoard(String boardId, String gadgetId) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(boardId), "null board id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(gadgetId), "null gadget id");

		return accountStore.removeGadgetFromBoard(getUserId(), boardId, gadgetId);
	}

	@Override
	public int getRecentDocumentsCount(List<String> groupId, List<DocumentType> types)
			throws PersistenceException {
		// Get last session date
		Date sessDate = accountStore.getAccountById(getUserId(), "lastSession").getLastSessionDate();
		System.out.print(sessDate);

		// Get number
		return documentStore.getRecentDocumentsCount(sessDate, groupId, types);
	}

	@Override
	public boolean updateGadget(String boardId, Gadget g) throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(boardId), "null board id");
		Preconditions.checkNotNull(g, "null gadget");

		return accountStore.saveGadget(getUserId(), boardId, g);
	}

	@Override
	public boolean updateGroupTestQuest(String id, String quest, String questAnswer) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "null group id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(quest), "null quest");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(questAnswer), "null answ");
		Preconditions.checkArgument(hasPermissions(id, AccountPermission.CAN_CHANGE_TEST_QUEST));

		return groupStore.updateTestQuest(id, quest, questAnswer);
	}

	@Override
	public boolean applyRoleToUser(String roleId, String userId, String groupId)
			throws PersistenceException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(roleId), "null role id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(userId), "null user id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(hasPermissions(groupId, AccountPermission.CAN_CHANGE_ACCOUNT_GROUP));

		// Update role
		if(!accountStore.applyRoleToUser(roleId, userId, groupId))
			return false;
		
		// Fire event
		onUserRoleUpdated(new Pair<Role, String>(groupStore.getRole(roleId, groupId), groupId),
				generateSubscriberNode("user", getUserId()));

		return true; 
	}

	@Override
	public void setDefaultRole(String groupId, Role role) {
		Preconditions.checkNotNull(role, "null role");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(role.getId()), "null role id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group id");
		Preconditions.checkArgument(hasPermissions(groupId, AccountPermission.CAN_CHANGE_ROLES));
		
		groupStore.setDefaultRole(groupId, role);
	}

	@Override
	public List<GroupProfile> getAllGroup(int offset, int limit) {
		return groupStore.getAllGroups(offset, limit);
	}

	@Override
	public boolean sendFeedback(String userName, String message) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(userName), "null user name");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(message), "null message");
		
		return emailSender.sendFeedback(userName, message);
	}
}